Assemblerkurs Teil 1 Hi! Ich hei~e Face Hugger und habe mich von GWM breitschlagen lassen Šin der CPI einen Assemblerkurs aufzuziehen. Da ich momentan bei der BW Šbin und gerade nichts besseres zu tun habe, sitze ich hier auf meiner Š"Stube" und schreibe diesen Mist. Ich wei~ garnicht genau, wie ich Šeuch den Senf beibringen soll, aber ich hoffe, da~ sich dieser Kurs Špositiv entwickelt. ]ber Kritik und Anregungen w}rde ich mich freuen. Š Als erstes stellt sich die Frage der Zielgruppe. Assemblerkenntnisse Šsind nicht notwendig. Ihr solltet euch allerdings etwas mit Bits und ŠBytes auskennen, Hexadezimalzahlen halbwegs beherrschen, vielleicht Šetwas }ber die Speicheraufteilung des CPC wissen und in Basic mehr Šzustandekriegen wie - 10 PRINT"Hallo ich heisse AOS!":GOTO 10 - Zum Assembler: Ich benutze den MAXAM-Assembler und werde mich in den ŠErkl{rungen auf diesen beschr{nken. Auf geht's: Die Register Die Register sind die Variablen der Maschinensprache. Ich beschr{nke Šmich ersteinmal auf die wichtigsten Register A, B, C, D, E, F, H, L. ŠDiese Register sind alle 8-Bit gro~ (oder breit, tief, hoch...). A ist Šder sogenannte Akkumulator. Viele Assemblerbefehle (Operationen) Šlassen sich nur mit ihm ausf}hren. F}r 16-Bit-Operationen werden die ŠRegister B+C, D+E und H+L zu sogenannten Doppelregister oder auch ŠRegisterpaaren zusammengegfa~t. Sie lauten dann eben BC, DE und HL, Šwobei HL wieder eine besondere Stellung einnimmt, da einige Befehle Šnur mit HL m|glich sind Die ersten Befehle Der CALL-Befehl: Diesen Befehl gibt es genauso in Basic. Er ruft ein ŠMaschinenprogramm auf, {hnlich wie mann mit GOSUB in Basic ein ŠBasic-Unterprogramm aufruft. Die Stelle im Speicher, wo das ŠMaschinenprogramm anf{ngt wird als sogenannter Parameter hinter dem ŠCALL angegeben. Diese "Stelle im Speicher" ist praktisch die ŠZeilennummer im Basic, nur da~ sie nicht Zeilennummer hei~t, sondern Š"Adresse" (hat nichts mit 'ner Wohnung zu tun). An der Adresse BB06 Šliegt ein Maschinenprogramm, da~ auf einen Tastendruck wartet. Mit dem ŠCALL-Befehl k|nnen wir es aufrufen. Gebt in Basic ein: CALL &BB06 Der Rechner wartet auf einen Tastendruck, es funktioniert also. Der RET-Befehl: Am Ende jedes Maschinenprogramms steht meistens ein ŠRET-Befehl, der nichts anderes zu bedeuten hat, wie der RETURN-Befehl Šin Basic. Er zeigt das Ende eines Maschinen(unter-)programms an. Der ŠComputer springt anschlie~end wieder zur}ck zum Hauptprogramm, wo der ŠCALL-Befehl ausgef}hrt wurde. Der LD-Befehl: Allgemein gesehen der meistgebrauchte Befehl; der ŠLade-Befehl. Der Syntax lautet LD ,. LD A,B hei~t z.B. "Lade A mit B", in Basic w{re das "A=B". Dieser ŠBefehl ist in vielf{ltiger Form auffindbar: LD D,E LD L,A LD C,L Šusw. . Man kann auch ein Register mit einem Wert (einer Konstanten) Šladen, z.B. LD B,10. Unser erstes Maschinenprogramm: Maxam starten (Assembler & Editor), IM eingeben, exteditor, dit ŠText. Nun gebt folgendes Programm ein: ORG &5000 ;dies ist kein Assemblerbefehl, sondern ein Š ;Hinweis, da~ der assemblierte Code bei der Š ;Adresse &5000 anfangen soll. LD A,2 ;A=2 CALL &BC0E ;BC0E -> Mode-Befehl. Im Akku steht der Š ;Bildschirmmode, hier Mode 2 RET ;fertig Wenn ihr dies eingetippt habt, ESC um ins Men} zur}ckzukommen und Šf}r "Assemble" dr}cken, das Programm wird assembliert, Taste dr}cken Šum ins Men} zur}ckzukommen. Nun dr}cken f}r "Jump to code". Unser ŠProgr{mmchen steht bei &5000, also 5000 eingeben. Siehe da, Mode 2 is Šborn! Weiteres zum LD-Befehl: Ihr habt bisher erst zwei Varianten des ŠLade-Befehls kennengelernt, n{mlich LD Register,Register und ŠLD‘Register,Wert. Es gibt noch viele andere M|glichkeiten: LD (Adresse),A ;Lade A in die Speicherstelle "Adresse" LD A,(Adresse) ;Lade A aus der Speicherstelle "Adresse" genauso: LD (HL),A LD A,(HL) LD (DE),A LD A,(DE) LD (BC),A LD A,(BC) hier wird lediglich die Adresse an die/aus der der Akku geladen wird Šdurch ein 16-Bit-Register angegeben. Beispiel: ORG &5000 LD A,255 ;A=255 LD (&C000),A ;A nach C000 laden, bei C000 f{ngt der Š ;Bildschirmspeicher an RET ;fertig Wenn ihr das Ding mal startet, m}~te links oben auf dem Bildschirm ein Škleiner l{nglicher Strich erscheinen. Der Wert 255 befindet sich nun Šan der Adresse C000. LD Doppelregister,16-Bit-Wert Mit diesem Befehl k|nnen wir den Lade-Befehl LD (HL),A testen. ORG &5000 LD A,255 ;A=255 LD HL,&C000 ;HL=C000 (nebenbei bemerkt: Die 8-Bit-Register Š ;einzeln betrachtet ist H=C0 und L=00, H (bzw. Š ;D oder B) ist das sog. Highbyte und L (bzw. E Š ;oder C) das Lowbyte. LD (HL),A ;Lade A nach (HL), also an die Adresse aus HL, Š ;also nach C000 RET Nun, wie zu Beginn erw{hnt, zu der besonderen Stellung des ŠHL-Registers: Neben LD (HL),A ist genauso LD (HL),B; LD (HL),E; LD (HL),H m|glich. ŠBei DE und BC existiert der Befehl nur mit A, LD (BC),E ist z.B. nicht Šm|glich!! LD (Adresse),A ist auch nur mit dem Akku m|glich LD (Adresse),C gibt's Šnicht! So das war's erstmal. Ist schon irgendjemand nicht mitgekommen? Im Šn{chsten Teil werde ich euch wieder ein paar neue Befehle erkl{ren und Šetwas }ber Flags erz{hlen. Au~erdem stelle ich euch mein erstes ŠAssemblerprogramm vor (mehr wie 3 Jahre alt), von dem ich so Šhingerissen war, da~ ich mir in einer Woche den ganzen Assemblersumpf Šreingeschl}rft habe. Assemblerkurs Teil 2 Gebt mal in BASIC ein: MODE 2:FOR I=&C000 TO &C0FE:POKE i,255:NEXT Langsam aber sicher erscheinen ein paar Linien auf dem Screen. Wie das Šganze in Assembler aussieht, zeige ich euch gleich, erstmal ein paar Šneue Befehle: INC register das Register wird um eins erh|ht (increment) DEC register das Register wird um eins verringert (decrement) DEC A w{re z.B. A=A-1 in BASIC. Der Befehl ist genauso auf 16-Bit-Register anwendbar z.B. INC HL oder DEC BC. JP Adresse Springe {hnlich dem CALL-Befehl zur angegebenen Adresse. In BASIC Švergleichbar mit dem GOTO-Befehl. Der Unterschied zum CALL-Befehl Šliegt darin, da~ hier nicht mit einem RET-Befehl zur}ckgesprungen Šwerden kann. JR Sprungdifferenz JR ist im Prinzip genauso wie der JP-Befehl, nur da~ nun relativ zur Šaktuellen Adresse gesprungen wird. H|rt sich kompliziert an, ist aber Šrecht einfach: JR 50 hei~t z.B. 'springe um 50 Bytes weiter'. Dies hat Šzwei Vorteile: 1. der Befehl ist um ein Byte k}rzer als der JP-Befehl. 2. Programme, die nur JPs statt JRs benutzen, laufen auch nur an der Š Adresse, an der sie assembliert wurden. Programme, die nur JRs Š benutzen sind an jeder beliebigen Adresse lauff{hig (andere Š Befehle, die mit absoluten Adressen arbeiten d}rfen dann nat}rlich Š auch nicht verwendet werden). Nachteil: die Sprungdifferenz ist nur -128 bis +127 gro~. Um das Ausrechnen der Sprungdifferenz k}mmert sich nat}rlich der ŠAssembler. Die Flags: Die Flags sind einzelne Bits im bisher noch nicht besprochenen F-Register. Diese Bits werden durch einige Befehle beeinflu~t. Die Šbeiden wichtigsten Flags sind das Carry-(]bertrags-)Flag und das Zero-Flag. Das Zero-Flag wird dann gesetzt, wenn z.B. beim INC/DEC-Befehl mit 8-Bit-Registern das entsprechende Register den Wert ŠNull erreicht. Bei den Sprung befehlen (CALL, RET, JP, JR) lassen sich Bedingungen Šankn}pfen, so da~ der Computer auf die Flagzust{nde reagieren kann, Š{hnlich wie IF THEN GOTO in BASIC. Die Bedingungen lauten Z (springe wenn Null (Zero)), NZ (springe wenn Šnicht Null (Not Zero)), C (springe bei ]bertrag (Carry)), NC (springe Šwenn kein ]bertrag (Not Carry)). Als Assemblersyntax sieht das so aus: ŠCALL NZ,Adresse oder z.B. RET Z. Am besten versteht ihr das an einem zusammenh{ngendem Programm: Mein erstes Assemberprogramm ORG &5000 LD B,255 ;B ist der Schleifenz{hler, B=255 LD HL,&C000 ;HL enth{lt die Bildschirmadresse, HL=&C000 LD E,255 ;der Wert 255 soll in den Bildschirm gepoket Š ;werden also E=255 loop LD (HL),E ;E (255) in den Bildschirm keulen INC HL ;HL (Bildschirmadresse um eins erh|hen DEC B ;B=B-1 -> Zero-Flag wird gesetzt wenn B=0 ist JR NZ,loop ;Springe nach 'loop' wenn Zero-Flag nicht Š ;gesetzt (B ungleich Null) RET ;fertig Wenn ihr das mit dem Basicprogramm vergleicht, werdet ihr einen Šenormen Geschwindigkeitsunterschied feststellen. 'loop' ist ein sog. ŠLabel, eine Art Sprungmarke. Man kann solche Labels auch zu Beginn Šdefinieren. Unser MODE 2 Progr{mmchen aus dem 1.Teil dieses Kurses Šk|nnte z.B. so aussehen: StMode EQU &BC0E ORG &5000 LD A,2 CALL StMode RET Nebenbei bemerkt: Bei diesen paar Assemblerzeilen w}rden die Profis Šschon aufschreien, da man das CALL und das nachfolgende RET zu einem ŠJP zusammenfa~t: LD A,2 JP StMode macht n{mlich dasselbe und spart ein Byte, oder wie ein altes Šbayrisches Sprichwort sagt: CALL auf RET doas gibtes net! Genauso ist in BASIC die Befehlsfolge GOSUB 1000:RETURN v|lliger ŠQuatsch, da hier ein GOTO 1000 hingeh|rt. Als Schleifenz{hler kann theoretisch jedes andere der gebr{uchlichen Š8-Bit-Register benutzt werden. F}r solche Schleifen stellt der Z80 Šallerdings einen speziellen Befehl zur Verf}gung: DJNZ Sprungdifferenz (bzw. in Assembler steht hier ein Label) Dieser Befehl tut nichts anderes wie die Befehlsfolge DEC B JR NZ,loop im Beispiellisting, ist aber ein Byte k}rzer und entsprechend Šschneller. Der DJNZ-Befehl benutzt das B-Register, deshalb wird dieses ŠRegister meistens als Schleifenz{hler benutzt. Zum Testen k|nnt ihr Šmal die besagten zwei Zeilen durch ein 'DJNZ loop' ersetzen. Nun mein erstes Progr{mmchen ist zwar sch|n, aber ziemlich sinnlos. ŠMan sollte vielleicht mal den ganzen Screen f}llen: Der Bildschirmspeicher ist &4000 gro~. Da ein 8-Bit-Register als ŠSchleifenz{hler nicht ausreicht, m}ssen wir diesesmal mit einem ŠDoppelregister arbeiten. Wir m}ssen also einfach den Befehl LD B,255 durch ein LD BC,&4000 und Šden Befehl DEC B durch DEC BC ersetzen. Doch da gibt es ein Problem: ŠWie bereits einmal angedeutet, beeinflu~t nur der INC/DEC-Befehl mit Š8-Bit-Registern das Zero-Flag. d.h. bei einem DEC BC w}rde der Šanschlie~ende Sprungbefehl JR NZ nicht korrekt funktionieren. Um zu testen ob ein 16-Bit-Register Null ist, bedienen wir uns dem ŠOR-Befehl: Syntax: OR 8-Bit-Register Rein {u~erlich fand ich diesen Befehl fr}her als Assembler-Rookie Šziemlich verwirrend, da er nur einen Parameter besitzt. Es ist aber Šganz einfach: Solche Operationen werden immer mit dem Akku ausgef}hrt, Šund das Ergebnis wird immer im Akku gespeichert. Der Befehl OR Šbedeutet wie im BASIC eine Oder-Verkn}pfung OR B hei~t z.B. A=A OR B. Der Akku wird mit dem B-Register ŠODER-Verkn}pft, und das Ergebnis wird im Akku gespeichert. F}r unser ŠBeispiel wichtig: die Flags werden beeinflu~t. Um nun das 16-Bit-Register BC auf Nullheit abzutesten, sind folgende Šzwei Zeilen notwendig LD A,B ;Lade A mit B OR C ;ODER-Verkn}pfung A mit C Im Endeffekt ist dies eine ODER-Verkn}pfung zwischen B und C. B ODER C bedeutet: Ist in B ODER in C irgendein Bit gesetzt, dann sind Šauch im Ergebnis entsprechend Bits gesetzt. B: 01000010 ODER C: 00011010 --------------------- Ergebnis: 01011010 Wie ihr seht, taucht nur dort ein Null-Bit auf, wo in B und C ein ŠNull-Bit steht. Das Ergebnis ist also nur dann Null, wenn B und C Šgleich Null sind. Alles zusammengefa~t sieht das Assemblerlisting zum Screenf}llen nun Šso aus: ORG &5000 LD BC,&4000 ;BC ist der 16-Bit-Schleifenz{hler, BC=&4000 LD HL,&C000 ;HL ist die Bildschirmadresse, HL=&C000 LD E,255 ;E ist das F}llbyte, E=255 loop LD (HL),E ;E in den Screen schmei~en INC HL ;Adresse auf dem Screen um eins erh|hen DEC BC ;Schleifenz{hler um eins verringern LD A,B ;Schleifenz{hler... OR C ;...ungleich Null? JR NZ,loop ;dann nach 'loop' RET ;fertig Nebenbei bemerkt: den Schleifenz{hler k|nnte man sich in diesem Fall Šsparen, da der Bildschirmspeicher bei &0000 aufh|rt (&FFFF+1=&0000). Optimiert m}~te das Listing dann so aussehen: ORG &5000 LD HL,&C000 ;HL=&C000 LD E,255 ;E=255 loop LD (HL),E ;(HL)=E INC HL ;HL=HL+1 LD A,H OR L ;HL ungleich 0 ? JR NZ,loop ;dann nach 'loop' RET ;fertig So das war's erstmal. Im n{chsten Teil werde ich euch wahrscheinlich Šetwas }ber Addition und Subtraktion erz{hlen und n{her auf die Šlogischen Verkn}pfungen (like OR) eingehen. Assemblerkurs Teil 3 Wie Versprochen nun etwas zu den Additions- und Subtraktionsbefehlen Šdes Z80: Auf 8-Bit-Ebene: ADD 8-Bit-Register Das entsprechende 8-Bit-Register wird auf das Akku addiert. Das ŠErgebnis wird im Akku gespeichert. Die Flags werden beeinflu~t. Š]berschreitet das Ergebnis den 8-Bit-Bereich, so wird ein ]bertrag Šerzeugt (Carry=1). ADD H ist z.B. A=A+H genauso funktioniert auch die Addition mit einer Konstanten ADD 29 ist z.B. A=A+29 Will man B=B+11 rechnen, so mu~ man erst das B-Register ins Akku Šladen, 11 addieren und dann das Ergebnis wieder nach B zur}ckladen: LD A,B ADD 11 LD B,A Au~erdem gibt es noch den Befehl 'ADC 8-Bit-Register'. Er funktioniert Šgenauso wie der ADD-Befehl, nur da~ hier zus{tzlich noch das Carry Šaufaddiert wird. Mit diesem Befehl l{~t sich eine 16-Bit-Addition Šrealisieren: BC=BC+DE: LD A,C ADD E ;Lowbytes addieren LD C,A LD A,B ADC D ;Highbytes addieren unter Ber}cksichtigung des ]bertrags LD B,A Alle Befehle funktionieren genauso in der Subtraktion. Sie hei~en SUB Šbzw. SBC. Ein ]bertrag wird hier erzeugt, wenn das Ergebnis Null Šunterschreitet. Addition & Subtraktion mit 16-Bit: Hierzu gibt es die Befehle: ADD HL,HL ;HL=HL+HL (HL*2) ADD HL,DE ;HL=HL+DE ADD HL,BC ;HL=HL+BC ADD HL,SP ;HL=HL+SP (SP= Stackpointer, kommt noch) ADD IX,IX ;IX=IX+IX (IX= Indexregister, kommt n{chstesmal) ADD IX,DE ;IX=IX+DE ADD IX,BC ;IX=IX+BC ADD IX,SP ;IX=IX+SP ADD IY,IY ;IY=IY+IY (IY= ebenfalls Indexregister) ADD IY,DE ;IY=IY+DE ADD IY,BC ;IY=IY+BC ADD IY,SP ;IY=IY+SP Das sind alle 16-Bit-ADD-Befehle, sie existieren genauso als ADC-Befehle. Subtraktion (hier gibt's kein SUB(!)): SBC HL,HL ;HL=HL-HL-Carry(!) SBC HL,DE ;HL=HL-DE-Carry(!) SBC HL,BC ;HL=HL-BC-Carry(!) SBC HL,SP ;HL=HL-SP-Carry(!) Vorsicht: Da der Befehl SUB (ohne Carry) nicht existiert mu~ vor dem ŠSBC-Befehl das Carry-Flag gel|scht werden, da sonst evtl. eins zuviel Šabgezogen wird. Hierzu benutzen wir den Befehl OR A (A=A OR A). Er Šver{ndert den Akku nicht und l|scht das Carry-Flag. So f}hrt also nur Šdie Befehlsfolge OR A SBC HL,DE zu einer Korrekten 16-Bit-Subtraktion HL=HL-DE. Wie auch bei den 8-Bit-Additions- und Subtraktionsbefehlen wird auch Šbei 16-Bit bei }ber- bzw. unterschreiten des 16-Bit-Bereichs das ŠCarry-Flag auf 1 gesetzt. Bin{re Logik: AND, OR, XOR Diese Befehle existieren nur auf 8-Bit-Ebene. Die Operationen Šerfolgen, wie schonmal erw{hnt, immer mit dem Akku. Allgemein: XOR 8-Bit-Register bzw. 8-Bit-Wert hei~t A=A XOR 8-Bit-Register bzw. 8-Bit-Wert genauso OR und AND. Was hei~t eigentlich A=A OR/AND/XOR Register? Der AND-Befehl: Beispiel: A=10110001 B=10010010 AND B (A=A AND B): Die Bits der beiden Register werden nun Ziffer f}r Ziffer verglichen Šund nach folgendem Schemna }bersetzt: Bit x aus Register A * Bit x aus Register B * Ergebnis ****************************************************************** 0 * 0 * 0 0 * 1 * 0 1 * 0 * 0 1 * 1 * 1 * * d.h. nur wenn in A und (and!) in B das jeweils verglichene Bit gesetzt Šist (=1!), dann ist auch das jeweilige Bit im Ergebnis gesetzt. Bit 7: A: (1)0 1 1 0 0 0 1 B: (1)0 0 1 0 0 1 0 Ergebnis A: 1 x x x x x x x Bit 6: A: 1(0)1 1 0 0 0 1 B: 1(0)0 1 0 0 1 0 A: 1 0 x x x x x x Bit 5: A: 1 0(1)1 0 0 0 1 B: 1 0(0)1 0 0 1 0 A: 1 0 0 x x x x x ...usw... Bit 0: A: 1 0 1 1 0 0 0(1) B: 1 0 0 1 0 0 1(0) A: 1 0 0 1 0 0 0 0 Wir sehen also: Nur wo in A und B eine '1' steht, steht auch im ŠErgebnis eine '1'. Der OR-Befehl: Beim OR-Befehl werden die Bits nach folgenden Schema verglichen: Bit in A * Bit in B * Erg. ******************************** 0 * 0 * 0 0 * 1 * 1 1 * 0 * 1 1 * 1 * 1 Nur wo in A oder (or!) in B das jeweilige Bit gesetzt ist, ist auch Šdas Ergebnis-Bit gesetzt. OR B (A=A OR B): A: 10110001 OR B: 10010010 ================== Erg. A: 10110011 Der XOR-Befehl (Exclusives Oder): Schema: Bit in A * Bit in B * Erg. ******************************** 0 * 0 * 0 0 * 1 * 1 1 * 0 * 1 1 * 1 * 0 Das entsprechende Bit im Ergebnis ist dann gesetzt, wenn die ŠBitzust{nde in A und B ungleich sind: XOR B A: 10110001 XOR B: 10010010 ================== Erg. A: 00100011 Nun fragt ihr euch, was man mit diesen Befehlen anfangen kann. Hier Šein kurzer Anri~: AND: l|schen von Bits (ausmaskieren) und abtesten von Bits AND %11110000 l|scht z.B. die untere H{lfte (Low-Nibble) des Akkus. ŠDas %-Zeichen ist in Maxam das Zeichen daf}r, da~ eine Bin{rzahl folgt Š(wie '&' bei Hexadezimalzahlen). In BASIC ist die '&X'. Da die oberen vier Bits (High-Nibble) }brig bleiben, k|nnte es auch in Šdiesem Fall ein Test dieser Bits sein, da nat}rlich das Zero-Flag Šbeeinflu~t wird (Carry wird immer gel|scht). OR: Setzen von Bits OR %11000000 setzt z.B. die obersten beiden Bits des Akkus. XOR: Wird h{ufig bei Sprite-Routinen benutzt, die die ŠHintergrundgrafik nicht zerst|ren sollen. Unser altes Beispiel: A: 10110001 (Byte aus der Hintergrundgrafik) B: 10010010 (Byte aus Sprite (reimt sich!)) (XOR B) A: 00100011 (Neues Bildschirmbyte) ... Sprite ist gesetzt... A: 00100011 (Bildschirmbyte) B: 10010010 (Byte aus Sprite (reimt sich immernoch!)) (XOR B) A: 10110001 (Altes Byte aus der Hintergrundgrafik) ... Sprite ist gel|scht... Man braucht nur eine Routine zum Setzen, da ein weiteres setzen das ŠSprite wieder l|scht. Solche Sprites sehen nat}rlich ver{ndert aus wenn sie }ber eine Grafik Šhuschen. Kennt ihr bestimmt aus einigen Games. Puh! Das war's erstmal. N{chstesmal vielleicht was }ber ŠIndexregister... Assemblerkurs Teil 4 Indexregister Wie im letzten Teil kurz angesprochen, besitzt der Z80 weitere 16-Bit-Register, die sogenannten Indexregister, sie hei~en IX und IY. ŠIch beschr{nke mich bei der Beschreibung erstmal auf IX, IY ist Šgenauso. LD IX,16-Bit-Wert funktioniert genauso wie LD HL,16-Bit-Wert Wir hatten gelernt, da~ man mit 'LD 8-Bit-Register,(HL)' bzw. 'LD (HL),8-Bit-Register' einen Wert in die Speicherstelle Šschreiben/lesen kann, die durch HL angegeben wird. Mit den Indexregistern funktioniert das genauso, nur da~ man noch Šeinen Offset angeben kann. Der allgemeine Syntax lautet: LD 8-Bit-Register,(IX+Offset) bzw. LD (IX+Offset),8-Bit-Register Beispiel: LD IX,&A000 LD A,(IX+5) Diese Befehlsfolge bezweckt, da~ der Akku mit dem Wert aus der ŠSpeicherstelle bei A005 geladen wird. Wozu ist das gut? Durch die Indexregister ist der Zugriff auf Tabellen wesentlich Šleichter. Beispiel: In einem Spiel f}r mehrere Personen werden alle Variablen (alles was Šdie Spieler unterscheidet) in einer Tabelle gespeichert. Das k|nnen Šsein: Anzahl der Leben, Score, X- und Y-Positionen, Spritenummern, ŠTimer oder auch Adressen von Unterroutinen, z.B. f}r die Tastatur- Šbzw. Joystickabfrage. Nun wird das ganze Spiel so geschrieben, da~ es Š}ber die Indexregister auf die Variablen zugreift. Der Spieler soll Šz.B. 100 Punkte bekommen: LD DE,100 ;DE=100 LD L,(IX+8) ;Score nach HL LD H,(IX+9) ;... ADD HL,DE ;Score=Score+100 LD (IX+8),L ;Score wieder speichern LD (IX+9),H ;... Die Hauptschleife sieht dann so aus: start: LD IX,PlayerTabelle1 CALL Hauptroutine LD IX,PlayerTabelle2 CALL Hauptroutine CALL Frame JP start Eine kleine Erg{nzung zu den bisher gelernten Befehlen: Wenn ihr das Byte an der Speicherstelle, die durch HL angegeben wird Šum eins verringern wollt, so m}~t ihr nach dem bisher Gelernten Šfolgendes Schreiben: LD A,(HL) DEC A LD (HL),A dies k|nnt ihr euch sparen, denn ein DEC (HL) tut's auch (ganz Šunflexibel ist der Z80 ja nun doch nicht). Genauso funktioniert OR (HL), AND (HL), XOR (HL), ADD (HL), ADC (HL), SUB (HL), SBC (HL), ŠINC (HL) und einige weitere Befehle, die ihr noch kennenlernt. Genauso funktioniert's mit den Indexregistern, also OR (IX+Offs), INC (IX+Offs) usw. . Mit BC und DE geht's nat}rlich nicht! DEC (BC) Šoder AND (DE) ist utopie! (wie war das noch mit der Flexibilit{t?!). Stackpointer, PUSH, POP: Ein weiters Register, da~ ihr noch nicht kennt, ist der Stackpointer Š(SP-Register). Beim Z80 passiert es euch schnell, da~ euch die ŠRegister ausgehen. Wenn ihr ein Unterprogramm aufruft das eure Šbelegten (Brote?) Register ver{ndert, so m}ssen diese irgendwo Šzwischengespeichert werden. Dies geht sehr schnell mit dem PUSH- und ŠPOP-Befehlen. Sie hei~en PUSH BC, PUSH DE, PUSH HL, PUSH IX, PUSH IY Šund PUSH AF bzw. POP BC, POP DE, usw. . Bei einem PUSH-Befehl wird das entsprechende 16-Bit-Register nach (SP-1) und (SP-2) gespeichert und das SP-Register um zwei verringert. ŠBeim POP-Befehl geschieht dasselbe, blo~ umgekehrt. Die Daten werden Švon (SP) und (SP+1) in das entsprechende 16-Bit-Register geladen und Šder Stackpointer wird um zwei erh|ht. Wie der Name 'Stack' schon sagt, Šwerden die Daten quasi gestapelt (Stackpointer=Stapelzeiger). Wenn die ŠDaten mit dem POP-Befehl wiedergeholt werden sollen, so mu~ der Stapel Šin der umgekehrten Reihenfolge des PUSHens wieder abgebaut werden. ŠEine Unterroutine s{he dann so aus: Unterroutine: PUSH HL PUSH DE PUSH BC ;HL,DE und BC retten ... blablabla ;Routine ... POP BC POP DE POP HL ;BC,DE und HL wiederholen RET Das Prinzip nennt man LIFO (Last In -> First Out, das letzte, was du Šauf den Stapel packst, kommt auch als erstes wieder runter). In dem ganzen Stapelzeigergewurschtel liegt auch eine schwerwiegende ŠAbsturzursache begraben. Wie ich euch schonmal erkl{rt habe, ruft ein ŠCALL ein Unterprogramm auf und ein RET springt an die alte Adresse Šzur}ck, Irgendwo mu~ sich der Computer ja diese R}cksprungadresse Šmerken. Und wo macht er das? Richtig! Auf dem Stack! Ein CALL f}hrt Špraktisch u.a. ein PUSH PC (PC? Program Counter, noch'n Register!) Šaus und ein RET ein POP PC. Wenn ich also in einer Unterroutine nicht Šgenauso viele PUSHs wie POPs habe, kommt der CPC mit seiner ŠR}cksprungadresse durcheinander und landet zu 99% im Nirvana (yeah!). Und wo ihr schonmal im Multi-Register-Lern-Modus seid, hier noch die Šletzten paar: R-Register: Das ist das Refresh-Register, l{~t sich mit dem Akku lesen und Šschreiben, also nur LD A,R und LD R,A. Das Refresh-Register ist Šblablabla - RAM-Refresh. .... Es ist praktisch ein Z{hler und wird Škontinuiertlich hochgez{hlt. Verwendung als Timer und f}r ŠZufallszahlen. I-Register: Das ist das Interrupt-Register. Genauso wie beim R-Register l{~t sich Šmit LD A,I und LD I,A darauf zugreifen. Dieses Register hat im CPC Škeine besondere Verwendung und kann als Zwischenspeicher f}r den Akku Šbenutzt werden. Etwas Allgemeines: Alle Ladebefehle beeinflussen die Flags nicht(!), au~er LD A,R und LD A,I !!! Im n{chsten Teil gibt's was }ber Bit-, Rotations- und Schiebe-Befehle. Assemblerkurs Teil 5 Jetzt wird's heftig! Im 5.Teil des Assemblerkurses gebe ich euch den ŠRest! Ich meine, ich erkl{re euch die restlichen Assemblerbefehle. ŠDanach, im 6.Teil, gibt's eine Komplett}bersicht mit Kurzerkl{rung, Šinclusive Flagver{nderungen und Laufzeiten. Auf geht's: Die Rotationsbefehle: RL s (s=A,B,C,D,E,H,L,(HL),(IX+Offs),(IY+Offs)) Rotiert s nach links durch das Carry-Flag, d.h. der Inhalt des Carry-Flags wird Bit 0 (b0), Bit 0 wird Bit 1 (b1), b1 wird b2, ... b6 wird b7 und b7 kommt ins Carry. Beispiel: b7 b6 b5 b4 b3 b2 b1 b0 Carry B: 0 1 1 0 1 1 0 1 1 RL B B: 1 1 0 1 1 0 1 1 0 Die Bits scrollen praktisch nach links durch das B-Register unter Šmiteinbeziehen der Carry-Flags. RR s Rotiert s nach rechts. b7 b6 b5 b4 b3 b2 b1 b0 Carry B: 0 1 1 0 1 1 0 1 1 RR B B: 1 0 1 1 0 1 1 0 1 RLC s Rotiert nur s, links, ohne Carry, d.h. b0 wird nicht vom Carry Šbestimmt, sondern von b7. b7 wird zus{tzlich noch ins Carry geschoben. b7 b6 b5 b4 b3 b2 b1 b0 Carry B: 0 1 1 0 1 1 0 1 x RLC B B: 1 1 0 1 1 0 1 0 0 RRC s Rotiert s rechts, ohne Carry. Das Carry-Flag wird von b0 bestimmt. b7 b6 b5 b4 b3 b2 b1 b0 Carry B: 0 1 1 0 1 1 0 1 x RRC B B: 1 0 1 1 0 1 1 0 1 Die gerade besprochenen Befehle existieren auch als Spezialbefehl f}r Šden Akku. Sie sind k}rzer und schneller. Die herk|mmlichen ŠBefehle beeinflussen allerdings alle Flags, die speziellen Befehle mit Šdem Akku greifen nur auf das Carry-Flag zu. Die Akkubefehle lauten RLA, RRA, RLCA, RRCA. Es ist also ein ŠUnterschied, ob ihr RLA schreibt oder RL A. RLD rotiert Nibble-weise den Inhalt der Speicherzelle, die durch HL Šangegeben wird mit dem Low-Nibble des Akkus nach links. A (HL) xxxxaaaa bbbbcccc RLD xxxxbbbb ccccaaaa Beispiel: A (HL) &79 &F3 RLD &7F &39 RRD rotiert (HL) und A Nibble-weise rechts. A (HL) xxxxaaaa bbbbcccc RRD xxxxcccc aaaabbbb Beispiel: A (HL) &79 &F3 RRD &73 &9F Schiebebefehle: Hiervon gibt's nur drei: SLA s Schiebt die Bits von s nach links, Bit 7, welches durch die ŠSchiebeaktion herausf{llt, wird vom Carry-Flag aufgefangen. Von rechts Škommt eine Null herein. Carry b7 b6 b5 b4 b3 b2 b1 b0 B: x 0 1 1 0 1 1 0 1 SLA B B: 0 1 1 0 1 1 0 1 0 SRA s Schiebt die Bits von s nach rechts. Das Carry wird vom herausfallenden ŠBit 0 bestimmt. Es wird keine Null von links hereingeschoben(!), Šsondern b7 bleibt bestehen. b7 b6 b5 b4 b3 b2 b1 b0 Carry B: 0 1 1 0 1 1 0 1 x SRA B B: 0 0 1 1 0 1 1 0 1 Beispiel f}r b7=1: b7 b6 b5 b4 b3 b2 b1 b0 Carry B: 1 0 0 1 0 0 1 0 x SRA B B: 1 1 0 0 1 0 0 1 0 SRL s genauso wie SRA, nur wird b7 gleich Null, d.h. von links wird eine ŠNull hereingeschoben. b7 b6 b5 b4 b3 b2 b1 b0 Carry B: 1 0 0 1 0 0 1 0 x SRA B B: 0 1 0 0 1 0 0 1 0 Bit-Befehle: Diese Befehle sind eigentlich recht einfach zu verstehen. SET Bitnr.,s Setzt Bit n in s Beispiel: b7 b6 b5 b4 b3 b2 b1 b0 E: 1 1 0 1 0 0 0 0 SET 2,E E: 1 1 0 1 0 (1) 0 0 RES Bitnr.,s L|scht Bit n ind s Beispiel: b7 b6 b5 b4 b3 b2 b1 b0 E: 1 1 0 1 0 0 0 0 RES 4,E E: 1 1 0 (0) 0 0 0 0 BIT Bitnr.,s Testet einen Bitzustand. Ist das getestete Bit gleich eins, so ist das ŠZero-Flag gel|scht (NZ), ist es gleich Null, so ist das Zero-Flag Šgesetzt (Z). Beispiel: b7 b6 b5 b4 b3 b2 b1 b0 E: 0 1 1 0 0 1 0 0 BIT 4,E JR Z,loop In diesem Fall w}rde der Computer nach 'loop' springen. Sonstiges... CP s (s=A,B,C,D,E,H,L,(HL),(IX+Offs),(IY+Offs),8-Bit-Zahl) Der CP-Befehl ist im Prinzip ein SUB-Befehl, nur da~ das Ergebnis Šnicht im Akku gespeichert wird. Er dient zum Vergleichen von zwei 8-Bit-Werten. CP B vergleicht z.B. den Akku mit B. Ist A kleiner als B (A=B), so ist das ŠCarry nicht gesetzt (NC). Ist A gleich B (A=B), dann ist das Zero-Flag Šgesetzt (Z), da in diesem Fall A-B=0 ist. Ist A ungleich B (A<>B), Šdann ist das Zero-Flag gel|scht (NZ). CP 8 JR C,mark hie~e springe wenn A<8 JR NC,mark hie~e springe wenn A>=8 JR Z,mark hie~e springe wenn A=8 JR NZ,mark hie~e springe wenn A<>8 Schaut euch folgendes Programm an: loop LD A,(HL) LD (DE),A INC HL INC DE DEC BC LD A,B OR C JR NZ,loop Dieses Programm ist eine Kopierschleife. Es kopiert von (HL) nach Š(DE). In BC steht die Anzahl der Bytes, die kopiert werden sollen. Daf}r gibt's einen Spezialbefehl: LDIR HL ist die Adresse der Quelldaten DE ist die Zieladresse BC ist die Anzahl der Bytes, die kopiert werden sollen Die Zeiger (HL und DE) werden dabei nach jedem kopierten Byte um eins Šerh|ht, der Code wird praktisch 'vorw{rts' kopiert. loop LD A,(HL) LD (DE),A DEC HL DEC DE DEC BC LD A,B OR C JR NZ,loop Ist dasselbe wie der Befehl LDDR. Dieser Befehl funktioniert genauso Šwie LDIR, nur werden die Bytes praktisch 'r}ckw{rts' kopiert, da die ŠPointer (HL und DE) jeweils verringert werden. Beispiel: ORG &5000 LD HL,&8000 ;von &8000 LD DE,&C000 ;nach &C000 LD BC,&4000 ;&4000 Bytes LDIR ;kopieren RET kopiert euch den Bereich von &8000-&BFFF in den Bildschirmspeicher per LDIR. Mit LDDR sieht das so aus: ORG &5000 LD HL,&BFFF ;Quelle &BFFF LD DE,&FFFF ;Ziel &FFFF (Ende des Bildschirmspeichers) LD BC,&4000 ;&4000 Bytes LDDR ;kopieren RET SCF Setzt das Carry-Flag. CCF Komplementiert (invertiert) das Carry-Flag. CPL Invertiert den Akku. Beispiel: A: 10110011 CPL A: 01001100 NEG Negiert den Akku A=0-A bzw. A=-A Wenn ihr z.B. B-A rechnen wollt, so m}~te man schreiben: LD C,A LD A,B SUB C stattdessen kann man auch schreiben SUB B ;A-B NEG ;-(A-B)=B-A! DI sperrt den Interrupt. EI l{~t den Interrupt wieder zu. EX HL,DE (bzw. im Assemblerbuch steht EX DE,HL) vertauscht HL und DE. EX (SP),HL EX (SP),IX EX (SP),IY vertauscht HL,IX,IY mit dem obersten Stapelelement. Wozu ist das gut? ŠWenn man z.B. BC mit IX vertausche will, so geht das so: PUSH BC EX (SP),IX POP BC EX AF,AF' Im Z80 existieren die Register A,F,B,C,D,E,H,L doppelt. Mit diesem ŠBefehl wird der Akku samt Flags mit seinem Doppelg{nger vertauscht. Diesen, sowie den nachfolgenden Befehl solltet ihr nur benutzen, wenn Šihr den CPC auch wirklich im Griff habt, da der zweite Registersatz Švom Betriebsystem benutzt wird! EXX Vertauscht BC, DE und HL mit den Registern aus dem zweiten Satz. NOP (im Speicher ein Null-Byte) macht garnichts. HALT h{lt die CPU (den Z80) an. (H{?). Und zwar solange bis der n{chste ŠInterrupt kommt. RST x (x=&00,&08,&10,&18,&20,&28,&30,&38) Der RST-Befehl springt an die Adresse x. Dies ist ein schneller CALL-Befehl, da ein RST nur ein Byte lang ist (CALL = 3 Bytes, ŠBefehlsbyte + zwei Bytes f}r die Adresse) Wird vom Betriebsystem benutzt. DAA Dezimalanpassung des Akkumulators, ziemlich ungebr{uchlicher Befehl. Beispiel: A: &09 ADD 1 DAA A: &10 (Normalerweise w{re A=&0A) Die Hexadezimalzahl sieht praktisch aus wie eine Dezimalzahl. IN r,(C) (r=A,B,C,D,E,H,L) L{dt r von der Hardware, die durch die Adresse des BC(!) Registers Šangegeben wird, eigentlich m}~te der Befehl also IN r,(BC) hei~en! Beispiel: LD B,&F5 ;Portadresse BC=&F5xx (Lowbyte ist nicht von ;Bedeutung) frame IN A,(C) ;Wert holen RRA ;b0 ins Carry rotieren JR NC,frame ;Wiederholen bis Carry gesetzt ist Diese Routine wartet auf das sogenannte Frame-Signal, das alle 50stel ŠSekunde ausgegeben wird. IN A,(n) L{dt den Akku von der Hardware, die durch A (Highbyte) und den Wert n Š(Lowbyte) angegeben wird. Beispiel: frame LD A,&F5 IN A,(0) RRA JR NC,frame s.o. OUT (c),r (r=A,B,C,D,E,H,L) Gibt r an die Hardware aus, die durch die Adresse aus BC angegeben Šwird. Beispiel gibt's sp{ter mal. Einige wenige Befehle fehlen noch. Diese werdet ihr aber in der Šversprochenen Gesamt}bersicht finden. Bis dann!